﻿using System;
using System.Collections.Generic;
using System.Text;
using NHibernate;
using Technoera.Common.QueryModel.Basic;
using NHibernate.Expression;
using Technoera.Common.QueryModel;

namespace Technoera.Common.Nh.QueryModel.Basic
{
    public class NhBasicQueryParser : INhQueryParser
    {
        #region INhQueryParser Members

        public ICriteria Parse(IQueryExpressionTree query, NhPersistenceContext persistenceContext)
        {
            if (query is BasicQueryExpressionTree)
            {
                return Parse((BasicQueryExpressionTree)query, persistenceContext);
            }
            else
            {
                throw new InvalidOperationException(string.Format("NhBasicQueryParser does not support query of type '{0}'.", query.GetType().FullName));
            }
        }

        #endregion

        private ICriteria Parse(BasicQueryExpressionTree query, NhPersistenceContext persistenceContext)
        {
            ICriteria criteria = persistenceContext.Session.CreateCriteria(query.RootEntityNode.EntityType);
            Dictionary<string, string> aliases = new Dictionary<string, string>();
            criteria.Add(GetCriterion(query.RootEntityNode.RootQueryItem, criteria, aliases));
            AddSortingRules(criteria, query, aliases);
            return criteria;
        }

        private ICriterion GetCriterion(IQueryItem queryItem, ICriteria criteria, IDictionary<string, string> aliases)
        {
            if (queryItem is IQueryCriterion)
            {
                if (queryItem is ScalarPropertyCriterion)
                {
                    return ParseQuery((ScalarPropertyCriterion)queryItem, criteria, aliases);
                }
                else if (queryItem is ScalarBetweenPropertyCriterion)
                {
                    return ParseQuery((ScalarBetweenPropertyCriterion)queryItem, criteria, aliases);
                }
                else
                {
                    throw new NotSupportedException(queryItem.ToString());
                }
            }
            else if (queryItem is IQueryConjunction)
            {
                ICriterion criterion1 = GetCriterion(((IQueryConjunction)queryItem).Expression1, criteria, aliases);
                ICriterion criterion2 = GetCriterion(((IQueryConjunction)queryItem).Expression2, criteria, aliases);
                if (queryItem is AndConjunction)
                {
                    return Expression.And(criterion1, criterion2);
                }
                else if (queryItem is OrConjunction)
                {
                    return Expression.Or(criterion1, criterion2);
                }
                else
                {
                    throw new NotSupportedException(queryItem.ToString());
                }
            }
            else
            {
                throw new NotSupportedException(queryItem.ToString());
            }
        }

        private ICriterion ParseQuery(ScalarBetweenPropertyCriterion query, ICriteria criteria, IDictionary<string, string> aliases)
        {
            string propertyName = GetPropertyName(query.PropertyPath, criteria, aliases);

            return Expression.Between(propertyName, query.Value1, query.Value2);
        }

        private string GetPropertyName(string propertyPath, ICriteria criteria, IDictionary<string, string> aliases)
        {
            if (propertyPath.Contains("."))
            {
                return GetNestedPropertyName(propertyPath, criteria, aliases);
            }
            else
            {
                return propertyPath;
            }
        }

        private ICriterion ParseQuery(ScalarPropertyCriterion query, ICriteria criteria, IDictionary<string, string> aliases)
        {
            string propertyName = string.Empty;
            if (query.PropertyPath.Contains("."))
            {
                propertyName = GetNestedPropertyName(query.PropertyPath, criteria, aliases);
            }
            else
            {
                propertyName = query.PropertyPath;
            }

            switch (query.Operator)
            {
                case QueryOperator.Eq:
                    return Expression.Eq(propertyName, query.Value);
                case QueryOperator.Low:
                    return Expression.Lt(propertyName, query.Value);
                case QueryOperator.LowEq:
                    return Expression.Le(propertyName, query.Value);
                case QueryOperator.Gr:
                    return Expression.Gt(propertyName, query.Value);
                case QueryOperator.GrEq:
                    return Expression.Ge(propertyName, query.Value);
                case QueryOperator.Like:
                    return Expression.Like(propertyName, query.Value);
                default:
                    throw new NotSupportedException(query.Operator.ToString());
            }
        }

        private string GetNestedPropertyName(string propertyPath, ICriteria criteria, IDictionary<string, string> aliases)
        {
            Int32 lastDotPosition = propertyPath.LastIndexOf('.');
            string parentPath = propertyPath.Substring(0, lastDotPosition);
            if (!aliases.ContainsKey(parentPath))
            {
                CreateAlias(parentPath, criteria, aliases);
            }
            return string.Format("{0}.{1}", aliases[parentPath], propertyPath.Substring(lastDotPosition + 1));

        }

        private void CreateAlias(string path, ICriteria criteria, IDictionary<string, string> aliases)
        {
            Int32 lastDotPosition = path.LastIndexOf('.');
            if (lastDotPosition > -1)
            {
                string parentPath = path.Substring(0, lastDotPosition);
                CreateAlias(parentPath, criteria, aliases);
            }
            string alias = string.Concat("Alias_#_", path.Replace(".", "_")); //Guid.NewGuid().ToString().Replace("-", string.Empty);
            criteria.CreateAlias(path, alias);
            aliases.Add(path, alias);
        }

        private void AddSortingRules(ICriteria criteria, BasicQueryExpressionTree query, Dictionary<string, string> aliases)
        {
            foreach (SortingRule rule in query.SortingRules)
            {
                string propertyName = GetPropertyName(rule.PropertyPath, criteria, aliases);
                if (rule.SortDirection == Technoera.Common.QueryModel.Basic.SortDirection.Ascending)
                {
                    criteria.AddOrder(Order.Asc(propertyName));
                }
                else
                {
                    criteria.AddOrder(Order.Desc(propertyName));
                }
            }
        }
    }
}
